/**
  * 
  * Videotify is a program used to convert video to flv and upload it to a server
  * 
  * Copyright (C) 2011 André Mörke
  *							  
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  * 
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  * 
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * 
  */

package de.osflex.videotify.business
{
	import de.osflex.videotify.constants.FFmpegParameters;
	
	import flash.desktop.NativeProcess;
	import flash.desktop.NativeProcessStartupInfo;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.NativeProcessExitEvent;
	import flash.events.ProgressEvent;
	import flash.filesystem.File;
	import flash.net.FileFilter;
	import flash.net.FileReference;
	import flash.system.Capabilities;
	
	import mx.controls.Alert;
	
	public class MainUIManager extends EventDispatcher
	{
		/**
		 * Convertmode fast/slow
		 */
		private var _currentConvertMode : String = "fast";
		/**
		 * Put some text in the ouputbox
		 */
		private var _strOutput : String = "";
		/**
		 * FileReference of the file dialogue
		 */
		private var _fileReference : File;
		
		/**
		 * FileReference of the selected file
		 */
		private var _file : File;
		
		/**
		 * NativeProcess
		 */
		private var _process : NativeProcess;
		
		/**
		 * System drive letter
		 */
		private var _systemDrive : String = '';
		
		
		/**
		 * The current status of the main ui
		 */		
		private var _stateStatus : String = 'home';
		
		/**
		 * Path of the converted video
		 */
		private var _pathToVideo : String = '';
		
		/**
		 * The File object of the converted file
		 */
		private var _convertedFile : File;
		
		/**
		 * Event constants
		 */
		private static const CONVERT_MODE_CHANGED : String = "convertModeChanged";
		private static const OUTPUT_CHANGED : String = "outputChanged";
		private static const FILE_SELECTED : String = "fileSelected";
		private static const STATE_STATUS_CHANGED : String = "stateStatusChanged";
		private static const VIDEO_READY : String = "videoReady";
		
		
		public function MainUIManager(target:IEventDispatcher=null){}
		
		/**
		 * Selected file to put into the model
		 */

		/**
		 * The current state of the main ui
		 */

		/**
		 * Path of the converted video
		 */		
		[Bindable(event="videoReady")]		
		public function get pathToVideo():String
		{
			return _pathToVideo;
		}

		[Bindable(event="stateStatusChanged")]
		public function get stateStatus():String
		{
			return _stateStatus;
		}

		[Bindable(event="fileSelected")]
		public function get file():File
		{
			return _file;
		}

		[Bindable(event="outputChanged")]
		public function get strOutput():String
		{
			return _strOutput;
		}

		[Bindable(event="convertModeChanged")]
		public function get currentConvertMode():String
		{
			return _currentConvertMode;
		}

		public function init() : void
		{
			if(NativeProcess.isSupported){
				this.streamOutput("native");
				prepareAllForFFMpeg();				
			}else{
				this.streamOutput('FFMpeg kann nicht ausgefuehrt werden (Kein NativeSupport)');
			}
		}
		
		/**
		 * Before ffmpeg is able to work some things have to be prepared
		 * 
		 * @return void
		 */
		
		private function prepareAllForFFMpeg() : void
		{
			if(Capabilities.os.toLowerCase().indexOf('win') > -1){
				prepareThePresets();	
				intoducePresetsToOS();
			}else if(Capabilities.os.toLowerCase().indexOf('mac') > -1){
				
			}
		}
		
		/**
		 * Attend to prepare the presets for ffmpeg
		 * 
		 * @return void
		 */
		
		private function prepareThePresets() : void
		{
			this._systemDrive = File.desktopDirectory.nativePath.substr(0, 3);
			var presetsPackage : File = new File(File.applicationDirectory.nativePath + '/presets');
			var file : File = new File(this._systemDrive);
			var target : File = file.resolvePath('ffmpeg_presets/.ffmpeg');			
			target.parent.createDirectory();			
			if(!target.exists){
				presetsPackage.copyTo(target, true);							
			}		
		}
		
		/**
		 * Set the environment variable to the presets
		 * 
		 * @return void		 
		 */
		
		private function intoducePresetsToOS() : void
		{
			var args : Vector.<String> = new Vector.<String>();
			args.push('/c setx HOME ' + this._systemDrive + 'ffmpeg_presets');
			this.startSystemCommand(args,false);			
		}
		
		/**
		 * Starts converting and uploading the selected file
		 * 
		 * @param _file File
		 * @return void
		 */
		
		public function convertAndUploadFile(_file : File,
											 _mode : String) : void
		{
			if(!_file){
				Alert.show('Bitte erst eine Datei auswählen!', 'Fehler');
				return;
			}			
			this.streamOutput('convertAndUploadFile(' + _file.name + ')');
			this._currentConvertMode = _mode;
			this.convertFile(_file);
		}
		
		/**
		 * Starts converting the file
		 * 
		 * @param _file File
		 * @return void
		 */
		
		private function convertFile(_file : File) : void
		{
			var args : Vector.<String> = new Vector.<String>();
			this._systemDrive = File.desktopDirectory.nativePath.substr(0, 3);			
			var sysfile : File = new File(this._systemDrive);
			var fileStorageDir : File = sysfile.resolvePath('tmp/.tmp');			
			fileStorageDir.parent.createDirectory();							
			
			
			var fileNameWithoutSuffix : String = _file.name.split('.')[0];
			args.push("-i");
			args.push(_file.nativePath);
			args.push('-y');
			var specificOptions : Array = new Array();
			specificOptions = String(FFmpegParameters.parameters[this.currentConvertMode]).split(' ');
			for(var i:uint=0; i<specificOptions.length; i+=2){
				args.push(specificOptions[i])
				args.push(specificOptions[i+1]);
			}
			_convertedFile = new File(fileStorageDir.parent.nativePath + '/' + fileNameWithoutSuffix + '.flv');
			this.streamOutput(_convertedFile.nativePath);
			args.push(_convertedFile.nativePath);
			
			
			traceOutArgs(args);
			var file : File = new File(File.applicationDirectory.nativePath);
			file = file.resolvePath("nativeApps");
			
			//file.resolvePath("nativeApps");
			if(Capabilities.os.toLowerCase().indexOf('win') > -1){
				file = file.resolvePath("win/bin/ffmpeg.exe");
				var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo(); 
				nativeProcessStartupInfo.executable = file;
				nativeProcessStartupInfo.arguments = args;				
				_process = new NativeProcess();			
				this.addListenersToProgress();				
				_process.start(nativeProcessStartupInfo);
				trace('running : ' + _process.running);
			}else if(Capabilities.os.toLowerCase().indexOf('mac') > -1){
				
			}
		}
		
		private function traceOutArgs(args : Vector.<String>):void
		{
			var str : String = '';
			for each(var part : String in args){
				str += part + ' ';
			}
			trace(str);
		}
		
		private function addListenersToProgress() : void
		{
			_process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onOutputData);
			_process.addEventListener(Event.STANDARD_ERROR_CLOSE, onError);
			_process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, onError);
			_process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, onError);
			_process.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, onError);
			_process.addEventListener(NativeProcessExitEvent.EXIT, onFFMpegExit);
		}
		
		private function onFFMpegExit(e:NativeProcessExitEvent) : void
		{
			this._pathToVideo = _convertedFile.nativePath;
			this.streamOutput(this._pathToVideo);
			dispatchEvent(new Event(VIDEO_READY));
			this._stateStatus = 'videoready';
			dispatchEvent(new Event(STATE_STATUS_CHANGED));
		}
		
		/**
		 * If an error occures while progress is running
		 * 
		 * @param e Event
		 * @return void
		 */
		
		private function onError(e: Event) : void
		{						
			trace(e.toString());
			var processError : String = _process.standardError.readUTFBytes(_process.standardError.bytesAvailable);
			trace(processError);
			this.streamOutput(processError);
		}
		
		/**
		 * Sets up a system command 
		 * 
		 * @param args Vector.<String>
		 * @param showOutput Boolean
		 * 
		 * @return void
		 */		 
		
		private function startSystemCommand(args : Vector.<String>,
											showOutput : Boolean) : void
		{			
			var shell : File = new File(this._systemDrive + 'Windows/system32/cmd.exe');			
			var nativeProcessInfo : NativeProcessStartupInfo = new NativeProcessStartupInfo();
			nativeProcessInfo.executable = shell;			
			nativeProcessInfo.arguments = args;
			_process = new NativeProcess();
			_process.start(nativeProcessInfo);
			if(showOutput){
				_process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onOutputData);
			}
		}	
		
		
		/**
		 * If a native process send a response
		 * it will be send to the outputbox of
		 * the main ui
		 * 
		 * @param event ProgressEvent
		 * @return void
		 */
		
		protected function onOutputData(event:ProgressEvent):void
		{
			this._strOutput += _process.standardOutput.readUTFBytes(_process.standardOutput.bytesAvailable); 
			/*var date:Date = new Date(); 
			this._strOutput += ' received at : ' + date.toString();*/
			dispatchEvent(new Event(OUTPUT_CHANGED));			
		}
		
		/**
		 * Initializes the file dialogue and opens it up
		 * 
		 * @return void
		 */
		
		public function openFileDialog() : void
		{
			this.streamOutput('openDialog');
			_fileReference = new File();
			addFileListeners();			
			_fileReference.browse([this.getFilters()]);
		}
		
		/**
		 * Adds some listeners to the file reference (file dialog)
		 * 
		 * @return void
		 */
		
		private function addFileListeners():void
		{
			_fileReference.addEventListener(Event.SELECT, onFileSelected);
			
		}
		
		/**
		 * Sets the selected file and fires the belonging event
		 * 
		 * @param event Event
		 * @return void
		 */
		protected function onFileSelected(event:Event):void
		{			
			this._stateStatus = 'home';
			dispatchEvent(new Event(STATE_STATUS_CHANGED));
			this._file = File(event.target);
			dispatchEvent(new Event(FILE_SELECTED));					
		}
		
		
		public function cancelConvertProcess() : void
		{
			if(this._process && this._process.running){
				this._process.exit(true);
			}
		}
		
		/**
		 * Every time a property was changes in the main ui this
		 * method will be called
		 * 
		 * @param _data Object
		 * @return void
		 */
		
		public function changeConvertProperty(_data : Object) : void
		{
			for (var part:String in _data){
				if(part == 'mode'){
					_currentConvertMode = _data[part];
					dispatchEvent(new Event(CONVERT_MODE_CHANGED));		
				}
			}		
			this.streamOutput('changeConvertProperty done');
			
		}
		
		/**
		 * Pushes the string into a variable an fires an event to
		 * notify all about the change of the variable
		 * 
		 * @param _str String
		 * @return void
		 */
		
		private function streamOutput(_str : String) : void
		{
			this._strOutput += _str + '\n';
			dispatchEvent(new Event(OUTPUT_CHANGED));
		}
		
		/**
		 * Sets some filters for files in the dialogue
		 * 
		 * @return FileFilter
		 */
		private function getFilters() : FileFilter
		{
			return new FileFilter('Video', "*.mts;*.avi;*.mpeg");
		}
		
	}
}